1   // Licensed under the Apache License, Version 2.0 (the "License");
2   // you may not use this file except in compliance with the License.
3   // You may obtain a copy of the License at
4   //
5   // http://www.apache.org/licenses/LICENSE-2.0
6   //
7   // Unless required by applicable law or agreed to in writing, software
8   // distributed under the License is distributed on an "AS IS" BASIS,
9   // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10  // See the License for the specific language governing permissions and
11  // limitations under the License.
12  
13  package org.apache.tapestry5.internal.plastic;
14  
15  import java.util.Map;
16  import java.util.Set;
17  
18  /**
19   * Used to track which methods are implemented by a base class, which is often needed when transforming
20   * a subclass.
21   */
22  public class InheritanceData
23  {
24      private final InheritanceData parent;
25  
26      private final String packageName;
27  
28      private final Set<String> methodNames = PlasticInternalUtils.newSet();
29      private final Map<String, Boolean> methods = PlasticInternalUtils.newMap();
30      private final Set<String> interfaceNames = PlasticInternalUtils.newSet();
31  
32      public InheritanceData(String packageName)
33      {
34          this(null, packageName);
35      }
36  
37      private InheritanceData(InheritanceData parent, String packageName)
38      {
39          this.parent = parent;
40          this.packageName = packageName;
41      }
42  
43      /**
44       * Is this bundle for a transformed class, or for a base class (typically Object)?
45       *
46       * @return true if this bundle is for transformed class, false otherwise
47       */
48      public boolean isTransformed()
49      {
50          return parent != null;
51      }
52  
53      /**
54       * Returns a new MethodBundle that represents the methods of a child class
55       * of this bundle. The returned bundle will always be {@linkplain #isTransformed() transformed}.
56       *
57       * @param packageName
58       *         the package that the child class will be created in
59       * @return new method bundle
60       */
61      public InheritanceData createChild(String packageName)
62      {
63          return new InheritanceData(this, packageName);
64      }
65  
66      /**
67       * Adds a new instance method. Only non-private methods should be added (that is, methods which might
68       * be overridden in subclasses). This can later be queried to see if any base class implements the method.
69       *
70       * @param name
71       *         name of method
72       * @param desc
73       *         describes the parameters and return value of the method
74       * @param samePackageOnly
75       *         whether the method can only be overridden in classes that are in the same package
76       */
77      public void addMethod(String name, String desc, boolean samePackageOnly)
78      {
79          methods.put(toValue(name, desc), samePackageOnly);
80          methodNames.add(name);
81      }
82  
83  
84      /**
85       * Returns true if this class or a transformed parent class contains an implementation of,
86       * or abstract placeholder for, the method.
87       *
88       * @param name
89       *         method name
90       * @param desc
91       *         method descriptor
92       * @return true if this class or a base class implements the method (including abstract methods)
93       */
94      public boolean isImplemented(String name, String desc)
95      {
96          return checkForMethod(toValue(name, desc), this);
97      }
98  
99      /**
100      * Returns true if the method is an override of a base class method
101      *
102      * @param name
103      *         method name
104      * @param desc
105      *         method descriptor
106      * @return true if a base class implements the method (including abstract methods)
107      */
108     public boolean isOverride(String name, String desc)
109     {
110         return checkForMethod(toValue(name, desc), parent);
111     }
112 
113     private boolean checkForMethod(String value, InheritanceData cursor)
114     {
115 
116         String thisPackageName = packageName;
117 
118         while (cursor != null)
119         {
120             if (cursor.methods.containsKey(value))
121             {
122                 boolean mustBeInSamePackage = cursor.methods.get(value);
123 
124                 if (!mustBeInSamePackage)
125                 {
126                     return true;
127                 }
128                 boolean isInSamePackage = thisPackageName.equals(cursor.packageName);
129 
130                 if (isInSamePackage)
131                 {
132                     return true;
133                 }
134             }
135 
136             cursor = cursor.parent;
137         }
138 
139         return false;
140     }
141 
142     /**
143      * Returns true if the class represented by this data, or any parent data, implements
144      * the named interface.
145      */
146     public boolean isInterfaceImplemented(String name)
147     {
148         InheritanceData cursor = this;
149 
150         while (cursor != null)
151         {
152             if (cursor.interfaceNames.contains(name))
153             {
154                 return true;
155             }
156 
157             cursor = cursor.parent;
158         }
159 
160         return false;
161     }
162 
163     public void addInterface(String name)
164     {
165         if (!interfaceNames.contains(name))
166         {
167             interfaceNames.add(name);
168         }
169     }
170 
171     /**
172      * Combines a method name and its desc (which describes parameter types and return value) to form
173      * a value, which is how methods are tracked.
174      */
175     private static String toValue(String name, String desc)
176     {
177         // TAP5-2268: ignore return-type to avoid methods with the same number (and type) of parameters but different
178         //            return-types which is illegal in Java.
179         // desc is something like "(I)Ljava/lang/String;", which means: takes an int, returns a String. We strip
180         // everything after the parameter list.
181         int endOfParameterSpecIdx = desc.indexOf(')');
182 
183         return name + ":" + desc.substring(0, endOfParameterSpecIdx+1);
184     }
185 
186     /**
187      * Returns the names of any methods in this bundle, or from any parent bundles.
188      */
189     public Set<String> methodNames()
190     {
191         Set<String> result = PlasticInternalUtils.newSet();
192 
193         InheritanceData cursor = this;
194 
195         while (cursor != null)
196         {
197             result.addAll(cursor.methodNames);
198             cursor = cursor.parent;
199         }
200 
201         return result;
202     }
203 }